#include <thread>
#include <sstream>
#include <numeric>

#include <HSModel/WorldUpdater.h>
#include <HSModel/World.h>
#include <HSModel/WorldObject.h>
#include <HSModel/Ground.h>
#include <HSModel/GeometryUtils.h>
#include <HSModel/Validator.h>
#include <HSModel/Human.h>
#include <HSModel/SharedConstants.h>
#include <HSModel/WorldInfoController.h>
#include <HSModel/HeightMapCoordinates.h>
#include <HSModel/Tree.h>
#include <HSModel/FerrisWheel.h>
#include <HSModel/GameController.h>
#include <HSModel/Balloon.h>
#include <HSModel/BalloonsShop.h>

WorldUpdater::WorldUpdater(World& i_world, GameController& i_game_controller)
  :m_world(i_world),
  m_game_controller(i_game_controller)
  {
  m_game_controller.LandscapeChanged(*m_world.GetGround());
  m_game_controller.SubscribeOnHumanCountChangedEvent([this](EventArguments e)
    {
    std::istringstream iss(e.GetString());
    size_t human_count;
    iss >> human_count;

    std::cout << "Humans: " << human_count << std::endl;

    this->_ChangeHumanCount(human_count);
    });
  }

void WorldUpdater::CreateHill(TVector2 i_point)
  {
  Ground* p_ground = m_world.GetGround();
  p_ground->CreateHighland(i_point, GroundConstants::HMAP_CHANGE);
  m_game_controller.LandscapeChanged(*m_world.GetGround());
  m_world.RefreshObjectsOnGroundPositions<Tree>();
  m_world.RefreshObjectsOnGroundPositions<FerrisWheel>();
  m_world.RefreshObjectsOnGroundPositions<BalloonsShop>();
  m_world.RefreshObjectsOnGroundPositions<Bench>();
  m_world.GenerateChangeEvent();
  }

void WorldUpdater::CreateHole(TVector2 i_point)
  {
  Ground* p_ground = m_world.GetGround();
  p_ground->CreateHole(i_point, GroundConstants::HMAP_CHANGE);
  m_game_controller.LandscapeChanged(*m_world.GetGround());
  m_world.RefreshObjectsOnGroundPositions<Tree>();
  m_world.RefreshObjectsOnGroundPositions<FerrisWheel>();
  m_world.RefreshObjectsOnGroundPositions<BalloonsShop>();
  m_world.RefreshObjectsOnGroundPositions<Bench>();
  m_world.GenerateChangeEvent();
  }

void WorldUpdater::CreateWalkway(TVector2 i_point)
  {
	Validator validator;
  if (validator.CanObjectBePlacedHere(TVector3(i_point.X(), i_point.Y(), m_world.GetWorldInfoController()->GetHeightForXY(i_point)), m_world, m_game_controller, WE_WALKWAY))
		{
		Ground* p_ground = m_world.GetGround();
		p_ground->CreateWalkway(i_point);

    m_game_controller.WalkwayCreated();
    m_world.GetWorldInfoController()->SetGroundType(i_point, GT_WALKWAY);
		m_world.GenerateLandscapeChangeEvent();
		}
  }

void WorldUpdater::AddGrass(TVector2 i_point)
  {
	Validator validator;
	if (validator.CanObjectBePlacedHere(TVector3(i_point.X(), i_point.Y(), m_world.GetWorldInfoController()->GetHeightForXY(i_point)), m_world, m_game_controller, WE_GRASS))
    {
		Ground* p_ground = m_world.GetGround();
    
    if(p_ground->GetGroundType(i_point) == GT_WALKWAY)
      m_game_controller.GrassAdded(true);
    else
      m_game_controller.GrassAdded();

    p_ground->SetGrass(i_point);
    m_world.GetWorldInfoController()->SetGroundType(i_point, GT_GRASS);
    m_world.GenerateLandscapeChangeEvent();
		}
  }

void WorldUpdater::AddTree(const TVector3& i_position)
  { 
  TVector3 center_position = HeightMapUtils::TransformPositionToCenterOfCell(i_position);
  Validator validator;
  if (validator.CanObjectBePlacedHere(center_position, m_world, m_game_controller, WE_TREE))
    { 
    m_world.AddTree(center_position);
    m_game_controller.TreePlaced();
    m_world.GenerateChangeObjectWithoutLandscapeEvent();
    }
  }

void WorldUpdater::AddFerrisWheel(const TVector3& i_position)
  {
  TVector3 center_position = HeightMapUtils::TransformPositionToCenterOfCell(i_position);
  Validator validator;
  if (validator.CanObjectBePlacedHere(center_position, m_world, m_game_controller, WE_FERRIS))
    { 
    m_world.AddFerrisWheel(center_position);
    m_game_controller.FerrisWheelPlaced();
    m_world.GenerateChangeObjectWithoutLandscapeEvent();
    }
  }

void WorldUpdater::AddBalloonsShop(const TVector3& i_position)
  {
  TVector3 center_position = HeightMapUtils::TransformPositionToCenterOfCell(i_position);
  Validator validator;
  if (validator.CanObjectBePlacedHere(center_position, m_world, m_game_controller, WE_BALLOONS_SHOP))
    { 
    m_world.AddBalloonsShop(center_position);
    m_game_controller.BalloonsShopPlaced();
    m_world.GenerateChangeObjectWithoutLandscapeEvent();
    }
  }

void WorldUpdater::AddBench(TVector2 i_point)
  {
  TVector3 center_position = HeightMapUtils::TransformPositionToCenterOfCell(TVector3(i_point.X(), i_point.Y(), 0));
  Validator validator;
  if (validator.CanObjectBePlacedHere(center_position, m_world, m_game_controller, EWorldEntityTypes::WE_BENCH))
    {
    m_world.AddBench(TVector2(center_position.X(), center_position.Y()));
    m_game_controller.BenchPlaced();
    m_world.GenerateChangeObjectWithoutLandscapeEvent();
    }
  }

void WorldUpdater::CreateWater(std::shared_ptr<Water> ip_water_model)
  {
  if (m_game_controller.CanCreateWater())
    {
    m_world.GetGround()->AddWaterModel(ip_water_model);
    m_world.GetWorldInfoController()->AddWater(ip_water_model);
    m_game_controller.WaterCreated();
    m_world.GenerateLandscapeChangeEvent();
    }
  }

bool WorldUpdater::MoveObjects(int i_timer_id)
  {
  auto all_objects = m_world.Movable();
  for (auto p_object : all_objects)
    {
    p_object->Move();
    m_world.GenerateMovedEvent(p_object->GetName());
    }
  return true;
  }

bool WorldUpdater::MakeProfit(int i_timer_id)
    {
    m_game_controller.MakeProfit();
  return true;
    }

bool WorldUpdater::ProcessAnimations(int i_timer_id)
    {
    for(auto it : m_world.Animated())
      {
      it->NextFrame();
			m_world.GenerateAnimateEvent(it->GetName());
      }		
  return true;
  }

void WorldUpdater::_ChangeHumanCount(size_t i_new_human_count)
  {
  m_game_controller.SetHumanCount(i_new_human_count);
  size_t old_human_count = m_world.GetItems<Human>().size();
  int delta = (int)i_new_human_count - (int)old_human_count;

  if(delta > 0)
    {
    _AddHumans(delta);
    }
  else if(delta < 0)
    {
    _RemoveHumans(-delta);
    }
  }

void WorldUpdater::_AddHumans(size_t i_number)
  {
  int curr_added = 0;
  TGroundTypeMap gt_map = m_world.GetGround()->GetGroundTypeMap();
  std::vector<size_t> x(gt_map.size());
  std::vector<size_t> y(gt_map[0].size());
  std::iota(x.begin(), x.end(), 0);
  std::iota(y.begin(), y.end(), 0);

  std::random_shuffle(x.begin(), x.end());
  std::random_shuffle(y.begin(), y.end());

  for(size_t i : x)
    {
    for(size_t j: y)
      {
      if(gt_map[i][j] == GT_WALKWAY && m_world.GetWorldInfoController()->IsCellFree(TCell(i, j)))
        {
        TVector3 human_position = HeightMapUtils::ConvertHMapPointToTVector(i, j, 0) + TVector3(GroundConstants::CELL_SIZE / 2, GroundConstants::CELL_SIZE / 2, 0);
        m_world.AddHuman(human_position);
        curr_added++;
        if(curr_added == i_number)
          {
          return;
          }
        }
      }
    }
  }

void WorldUpdater::_RemoveHumans(size_t i_number)
  {
  int curr_deleted = 0;
  for(auto human : m_world.GetItems<Human>())
    {
    m_world.GetWorldInfoController()->DeleteHumanPosition(human->GetName());
    m_world.DeleteObj(human);
    curr_deleted++;
    if(curr_deleted == i_number)
      break;
    }
  return;
  }

void WorldUpdater::DestroyObject(const TVector2& i_position)
  {
  WorldObject* obj = m_world.GetWorldInfoController()->GetObjectByPosition(i_position);
  if (obj == nullptr)
    return;

  m_game_controller.ObjectDestroyed(obj);
  m_world.GetWorldInfoController()->RemoveObject(obj);

  m_world.DeleteObj(obj);  
  m_world.GenerateChangeEvent();

  }

void WorldUpdater::RemoveWater(size_t water_model_index)
  {
  m_world.GetWorldInfoController()->RemoveWater(m_world.GetGround()->GetWaterModels()[water_model_index]);

  m_world.GetGround()->RemoveWaterModel(water_model_index);
  
  m_game_controller.WaterRemoved();
  m_world.GenerateChangeEvent();
  }
